﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Security;
using System.Diagnostics;
using System.Configuration.Provider;
using NHibernate;
using NHibernate.Linq;
using WebPattern.Core.Validators;
using WebPattern.DataAccess;

namespace WebPattern.Core.Security
{
    public class WebPatternRoleProvider : RoleProvider
    {
        #region Members

        #endregion

        #region Overriden Propeties
        public override string ApplicationName { get; set; }
        #endregion

        #region Overriden Methods
        public override bool RoleExists(string roleName)
        {
            Debug.WriteLine("RoleExists(" + roleName + ")");

            using (var session = NHibernateSessionManager.OpenSessionTransaction())
            {
                bool _roleExists = session.Query<Role>()
                        .Where(r => r.Name == roleName)
                        .Take(1)
                        .Any();

                session.Transaction.Commit();
                return _roleExists;
            }
        }
        
        public override void CreateRole(string roleName)
        {
            Debug.WriteLine("CreateRole(" + roleName + ")");

            RoleValidate(roleName);            

            if (this.RoleExists(roleName))
                throw new ProviderException("Role name already exists");

            using (var session = NHibernateSessionManager.OpenSessionTransaction())
            {
                session.Save(new Role { Name = roleName, ApplicationName = this.ApplicationName, Enabled = true });
                session.Transaction.Commit();
            }
        }

        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            Debug.WriteLine("DeleteRole(" + roleName + ",throwOnPopulatedRole=" + throwOnPopulatedRole + ")");

            RoleValidate(roleName);

            if (this.RoleExists(roleName) == false)
                throw new ProviderException("Role '" + roleName + "' not exist");

            if (throwOnPopulatedRole && GetUsersInRole(roleName).Length > 0)
                throw new ProviderException("Cannot delete a populated role");

            using (var session = NHibernateSessionManager.OpenSessionTransaction())
            {                
                session.Delete(session.Query<Role>().Where(r => r.Name == roleName).First());
                session.Transaction.Commit();
            }

            return true;
        }

        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            Debug.WriteLine("FindUsersInRole(" + roleName + "," + usernameToMatch + ")");

            RoleValidate(roleName);

            using (var session = NHibernateSessionManager.OpenSessionTransaction())
            {
                Role _role = session.Query<Role>()
                        .Where(u => u.Name == roleName)
                        .FirstOrDefault();

                if (_role == null) 
                    throw new ProviderException("Role '" + roleName + "' not exist");

                string[] _usernames = _role.UsersInRole
                    .Where(u => u.Name == usernameToMatch)
                    .Select(u => u.Name)
                    .ToList<string>()
                    .ToArray();

                session.Transaction.Commit();
                return _usernames;
            }
        }

        public override string[] GetAllRoles()
        {
            Debug.WriteLine("GetAllRoles()");

            using (var session = NHibernateSessionManager.OpenSessionTransaction())
            {
                string[] _roles = session.Query<Role>()
                        .Select(r => r.Name)
                        .ToList<string>().ToArray();
                
                session.Transaction.Commit();
                return _roles;
            }
        }

        public override string[] GetRolesForUser(string username)
        {
            Debug.WriteLine("GetRolesForUser(" + username + ")");

            UsernameValidate(username);

            using (var session = NHibernateSessionManager.OpenSessionTransaction())
            {
                User _user = session.Query<User>()
                    .Where(u => u.UserName == username)
                    .FirstOrDefault();

                if (_user == null) 
                    throw new ProviderException("Username '" + username + "' not exist");

                var a = _user.UserRoles.Select(r => r);
                string[] _userRoles = _user.UserRoles.Select(r => r.Name)
                    .ToList<string>()
                    .ToArray();

                session.Transaction.Commit();
                return _userRoles;
            }
        }

        public override string[] GetUsersInRole(string roleName)
        {
            Debug.WriteLine("GetUsersInRole(" + roleName + ")");

            RoleValidate(roleName);

            using (var session = NHibernateSessionManager.OpenSessionTransaction())
            {
                Role _role = session.Query<Role>()
                    .Where(u => u.Name == roleName)
                    .FirstOrDefault();

                if (_role == null) 
                    throw new ProviderException("Role '" + roleName + "' not exist");

                string[] _usernames = _role.UsersInRole.Select(r => r.UserName)
                    .ToList<string>()
                    .ToArray();

                session.Transaction.Commit();
                return _usernames;
            }
        }

        public override bool IsUserInRole(string username, string roleName)
        {
            Debug.WriteLine("IsUserInRole(" + username + ", " + roleName + ")");

            UsersRolesValidate(username, roleName);

            if (!RoleExists(roleName)) 
                throw new ProviderException("Role name '" + roleName + "' not exist");
            
            using (var session = NHibernateSessionManager.OpenSessionTransaction())
            {
                User _user = session.Query<User>()
                    .Where(u => u.UserName == username)
                    .FirstOrDefault();

                if (_user == null) 
                    throw new ProviderException("Username '" + username + "' not exist");

                bool _isUserInRole = _user.IsInRole(roleName);

                session.Transaction.Commit();
                return _isUserInRole;
            }
        }

        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            Debug.WriteLine("AddUsersToRoles(..., ...)");

            UsersRolesValidate(usernames: usernames, roleNames: roleNames);

            using (var session = NHibernateSessionManager.OpenSessionTransaction())
            {
                List<User> _users = session.Query<User>()
                    .Where(u => usernames.Contains(u.UserName))
                    .ToList<User>();

                List<Role> _roles = session.Query<Role>()
                    .Where(q => roleNames.Contains(q.Name))
                    .ToList<Role>();

                session.SetBatchSize(25);

                foreach (var user in _users)
                    foreach (var role in _roles)
                    {
                        user.UserRoles.Add(role);
                    }

                session.Transaction.Commit();
            }
        }

        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            Debug.WriteLine("RemoveUsersFromRoles(..., ...)");

            UsersRolesValidate(usernames: usernames, roleNames: roleNames);

            using (var session = NHibernateSessionManager.OpenSessionTransaction())
            {
                List<User> _users = session.Query<User>()
                    .Where(u => usernames.Contains(u.UserName))
                    .ToList<User>();

                List<Role> _roles = session.Query<Role>()
                    .Where(r => roleNames.Contains(r.Name))
                    .ToList<Role>();

                foreach (var user in _users)
                    foreach (var role in _roles)
                    {
                        user.UserRoles.Remove(role);
                    }

                session.Transaction.Commit();
            }
        }
        #endregion

        #region Helper Methods
        /// <summary>
        /// Throw new ArgumentException if parameters is not valid
        /// </summary>
        /// <param name="usernames">usernames</param>
        /// <param name="roleNames">roleNames</param>
        private void UsersRolesValidate(string[] usernames, string[] roleNames)
        {
            if (usernames.Any(r => string.IsNullOrWhiteSpace(r)))
                throw new ArgumentException("Username cannot be null or a whitespace string");

            if (roleNames.Any(r => string.IsNullOrWhiteSpace(r)))
                throw new ArgumentException("Role name cannot be null or a whitespace string");
        }
        /// <summary>
        /// Throw new ArgumentException if parameters is not valid
        /// </summary>
        /// <param name="username">username</param>
        /// <param name="roleName">roleName</param>
        private void UsersRolesValidate(string username, string roleName)
        {
            UsersRolesValidate(new string[] { username }, new string[] { roleName });
        }
        /// <summary>
        /// Throw new ArgumentException if parameters is not valid
        /// </summary>
        /// <param name="roleName">roleName</param>
        private void RoleValidate(string roleName)
        {
            if (string.IsNullOrWhiteSpace(roleName))
                throw new ArgumentException("Role name cannot be null or be a whitespace string");

            if (roleName.Contains(','))
                throw new ArgumentException("Role names cannot contain commas");
        }
        /// <summary>
        /// Throw new ArgumentException if parameters is not valid
        /// </summary>
        /// <param name="username">username</param>
        private void UsernameValidate(string username)
        {
            if (!StaticValidators.UsernameValidator(username))
                throw new ArgumentException("Username cannot be null or a whitespace string");
        }
        #endregion
    }
}
